#include <io.h>
#include <user.h>
#include <ts7200.h>
#include <defs.h>
#include <curses.h>
#include <string.h>
#include <commands.h>

/* USEFUL STATUS-TRACKING-RELATED FUNCTIONS */

void SensorInit( sensor *ss, char set, char b1, char b2 ) {
  ss->set = set;
  unsigned short val = (b1 << 8) | b2;
  int bit = 16;
  while (!(val & 1) && bit > 0) {
    val >>= 1;
    bit--;
  }
  ss->id = bit;
}

void FillSensorData(char *msg, sensor *data) {
  int last = -1;
  int i;
  for (i = 0; i < 5; i++) {
    if (msg[2*i] || msg[2*i+1])
      last = i;
  }
  SensorInit(data, 'A'+last, msg[2*last], msg[2*last+1]);
}

/* FORWARD DECLARATIONS FOR SYSTEM SERVERS */

void NameServer();
void ClockServer();
void RandomServer();
void TrainRxServer();
void TrainTxServer();
void TerminalRxServer();
void TerminalTxServer();
void IdleTask();

/* SHELL HELPER FUNCTIONS */

int ParseInput(char *buf, char **cmd, char **a1, char **a2) {
  char *pos;
  *cmd = StringToken(buf, &pos, ' ');
  if (*cmd == NULL) return 0;
  *a1 = StringToken(NULL, &pos, ' ');
  if (*a1 == NULL) return 1;
  *a2 = StringToken(NULL, &pos, ' ');
  if (*a2 == NULL) return 2;
  return 3;
}

/* SHELL COMMAND PROCESSOR */

int DoCommand(track *sts, char *cmd_buf) {
  char *cmd, *a1, *a2;
  int argc, v1, v2;
  argc = ParseInput(cmd_buf, &cmd, &a1, &a2);
  if (StringCompare(cmd, "tr", 2)) {
    if (argc != 3)
      return 1;
    v1 = Atoi(a1); v2 = Atoi(a2);
    if (v1 < 1 || v1 > 80)
      return 1;
    if (v2 < 0 || v2 > 14)
      return 1;
    Tr(sts, v1, v2);
  } else if (StringCompare(cmd, "rv", 2)) {
    if (argc != 2)
      return 1;
    v1 = Atoi(a1);
    if (v1 < 1 || v1 > 80)
      return 1;
    Rv(sts, v1);
  } else if (StringCompare(cmd, "sw", 2)) {
    if (argc != 3)
      return 1;
    v1 = Atoi(a1);
    if (v1 < 0 || v1 > 255)
      return 1;
    if (a2[0] != 'S' && a2[0] != 'C')
      return 1;
    Sw(sts, v1, a2[0]);
  } else if (StringCompare(cmd, "stop", 4)) {
    Stop(sts);
  } else if (StringCompare(cmd, "go", 2)) {
    Go(sts);
  } else if (StringCompare(cmd, "q", 1)) {
    FingerOfDeath();
  } else {
    return 1;
  }
  return SUCCESS;
}

/* WINDOW MESSAGE TYPES */

typedef struct __wbounds {
  int x1, x2, y1, y2;
} wbounds;

typedef struct __wcursor {
  int x, y;
} wcursor;

/* WINDOW HELPER FUNCTIONS */

void WindowInit( track *sts ) {
  ResetScreen();
  ClearScreen();
  SetScreenColor(FG_BASE+WHITE_OFFSET, BG_BASE+BLACK_OFFSET);
  // AcquireLock(SECTION_TERMINAL_TX);
  SetCursorXY(0, 1);
  Printf(""
"                                                                           \r\n"
"    _/_/_/_/                                _/          _/_/      _/_/_/  \r\n" 
"   _/    _/    _/_/_/  _/    _/    _/_/_/  _/        _/    _/  _/          \r\n"
"  _/_/_/    _/_/      _/    _/  _/        _/_/_/    _/    _/    _/_/       \r\n"
" _/            _/_/  _/    _/  _/        _/    _/  _/    _/        _/      \r\n"
"_/        _/_/_/      _/_/_/    _/_/_/  _/    _/    _/_/    _/_/_/         \r\n"
"                        _/                                                 \r\n"
"                   _/_/                                                    \r\n");

  Printf("\r\n");
  Printf("The Window System for PsychOS\r\n");
  Printf("Initializing...\r\n");
  Printf("Stopping trains...\r\n");
  Tr(sts, 9, 0);
  Tr(sts, 24, 0);
  Tr(sts, 25, 0);
  Tr(sts, 46, 0);
  Tr(sts, 55, 0);
  Printf("Setting switches...\r\n");
  int i;
  for (i = 1; i <= 156; i++) {
    if (i == 19)  // skip non-existent switches
      i = 153;
    FlipSwitch(sts, i, 'C');
  }
  sts->ss_last = -1;
  // Delay(50);      // force pause to allow train controller to catch up
  Printf("Press any key to start\r\n");
  // ReleaseLock(SECTION_TERMINAL_TX);
  Getc(COM2);

  ClearScreen();
}

void WindowDraw(int x1, int x2, int y1, int y2) {
  // AcquireLock(SECTION_TERMINAL_TX);
  int i;
  // draw bottom edge
  if (y2 < LINES) {
    SetCursorXY(x1, y2);
    for (i = x1+1; i < x2; i++)
      Putc(COM2, '_');
    Putc(COM2, '\0');
  }
  // draw right edge
  if (x2 < COLUMNS) {
    for (i = y1+1; i <= y2; i++) {
      SetCursorXY(x2-1, i);
      Printf("|");
    }
  }
  // ReleaseLock(SECTION_TERMINAL_TX);
}

/* EVENT GENERATORS
 *
 * Unlike the passive hardware-level Notifiers, these actively poll the
 * appropriate servers or hardware to generate software-level events. A
 * software-level event is one that does not trigger a hardware interrupt.
 */

void TickGenerator() {
  event msg;

  int manager_tid = WhoIs("window-manager");
  
  for ( ; ; ) {
    Delay(2);
    msg.ii = Time()/2;
    
    // message: current time
    // reply: NULL
    Send(manager_tid, (char*)&msg, sizeof(event), NULL, 0);
  }
  Exit();
}


// check the triggered sensor
void checkSensor(char *sensors)
{	
  char nums[10];
  /*
  // reorder the bytes
  nums[0] = sensors[1];
  nums[1] = sensors[2];
  nums[2] = sensors[3];
  nums[3] = sensors[4];
  nums[4] = sensors[5];
  nums[5] = sensors[6];
  nums[6] = sensors[7];
  nums[7] = sensors[8];
  nums[8] = sensors[9];
  nums[9] = sensors[0];
  */
  
  
  nums[0] = sensors[0];
  nums[1] = sensors[1];
  nums[2] = sensors[2];
  nums[3] = sensors[3];
  nums[4] = sensors[4];
  nums[5] = sensors[5];
  nums[6] = sensors[6];
  nums[7] = sensors[7];
  nums[8] = sensors[8];
  nums[9] = sensors[9];
  
  // helper values
  char banks[10];
  banks[0] = 'A';
  banks[1] = 'A';
  banks[2] = 'B';
  banks[3] = 'B';
  banks[4] = 'C';
  banks[5] = 'C';
  banks[6] = 'D';
  banks[7] = 'D';
  banks[8] = 'E';
  banks[9] = 'E';

  char lastSensorChar, lastSensorNum;

  int i, j;
  for (i = 0; i < 10; i++)
  {
	  for (j = 0; j < 8; j++)
	  {
		  if (nums[i] & (1 << (7 - j)))
		  {
			  lastSensorChar = banks[i];
			  lastSensorNum = j + 1 + (8 * (i % 2));
		  }
	  }
  }
    
  // AcquireLock(SECTION_TERMINAL_TX);
    SaveCursor();
    SetCursorXY(66, 6);
    Printf("   ");
    SetCursorXY(66, 6);
    Printf("%c%d", lastSensorChar, lastSensorNum);
    LoadCursor();
  // ReleaseLock(SECTION_TERMINAL_TX);  
}

void SensorGenerator() {
  int diff, c, i;
  char last[10];
  for (i = 0; i < 10; i++)
    last[i] = 0x00;
  char curr[10];
  event msg;

  int manager_tid = WhoIs("window-manager");
  
  for ( ; ; ) {
    Delay(2);
    // AcquireLock(SECTION_TRAIN_TX);
    Putc(COM1, TRAIN_SENSORS_BASE + NUM_SENSOR_MODULES);
    // ReleaseLock(SECTION_TRAIN_TX);
    diff = FALSE;
    for (i = 0; i < 10; i++) {
      c = Getc(COM1);
      curr[i] = c;
      // report only the difference of sensors
      msg.cc[i] = c; //curr[i] & ~(last[i]);
      if (msg.cc[i])
        diff = TRUE;
    }
    // checkSensor(curr);  
    if (diff) {
      // message: state of sensor modules polled
      // reply: bitfield describing which sensor modules to poll next
      Send(manager_tid, (char*)&msg, sizeof(event), NULL, 0);
    }
    
    // MemCopy(last, curr, 10);
  }
  Exit();
}

void InputGenerator() {
  event msg;

  int manager_tid = WhoIs("window-manager");

  for ( ; ; ) {
    msg.ii = Getc(COM2);

    // filter non-alphanumeric characters
    if (!IsAlnum(msg.ii) && msg.ii != '\r' && msg.ii != '\x7f')
      continue;

    // message: next character for shell
    // reply: NULL
    Send(manager_tid, (char*)&msg, sizeof(event), NULL, 0);
  }
}

/* WINDOW TASKS */

void TrackWindow() {
  // setup window
  WindowDraw(0, 64, 0, 22);   // track
  // UpdateSwitchDisplay(sts);
  /*
  // AcquireLock(SECTION_TERMINAL_TX);
  SetCursorXY(0, 1);
  Printf(""
"|====================================================\r\n"
"              /                                      \\\r\n"
"|=============   ==================================== \\\r\n"
"        /       /            \\         /             \\ |\r\n"
"       /       /              \\       /               \\|\r\n"
"      /       /                \\  =  /                 |\r\n"
"      |      |                  \\ | /                   \\\r\n"
"      |      |                   \\|/                     |\r\n"
"      |      |                    |                      |\r\n"
"      |      |                    |                      |\r\n");
"      |      |                    |                      |\r\n"
"      |      |                   /|\\                     |\r\n"
"      |      |                  / | \\                   /\r\n"
"      |      |                 /  |  \\                 /\r\n"
"      \\       \\               /   =   \\                |\r\n"
"       \\       \\             /         \\              /|\r\n"
"        \\       \\===================================== |\r\n" 
"|========\\       \\                                    /\r\n" 
"|==============   ====================================\r\n"
"               \\         \\                  /\r\n"
"|==================================================|");
  
  // ReleaseLock(SECTION_TERMINAL_TX);
  */
  
  int manager_tid = WhoIs("window-manager");

  for ( ; ; ) {
    Send(manager_tid, NULL, 0, NULL, 0);
    // update
  }

  Exit();
}

void TimerWindow() {
  int reply;

  WindowDraw(64, 80, 0, 3);   // timer

  int manager_tid = WhoIs("window-manager");

  for ( ; ; ) {
    // message: NULL
    // reply: number of ticks elapsed since start
    Send(manager_tid, NULL, 0, (char*)&reply, sizeof(int));
    
    // AcquireLock(SECTION_TERMINAL_TX);
      SaveCursor();
      SetCursorXY(68, 2);
      Printf("%d%d:%d%d.%d", reply/6000, (reply/600)%10,
          (reply/100)%6, (reply/10)%10, reply%10);
      LoadCursor();
    // ReleaseLock(SECTION_TERMINAL_TX);
  }

  Exit();
}

void SensorWindow() {
  char reply[10];
  sensor last;

  WindowDraw(64, 80, 3, 8);   // sensor
  // AcquireLock(SECTION_TERMINAL_TX);
    SaveCursor();
    SetCursorXY(66, 4);
    Printf("Last Sensor");
    LoadCursor();
  // ReleaseLock(SECTION_TERMINAL_TX);
  
  int manager_tid = WhoIs("window-manager");

  for ( ; ; ) {
    Send(manager_tid, NULL, 0, reply, 10);
    FillSensorData(reply, &last);
    // AcquireLock(SECTION_TERMINAL_TX);
      SaveCursor();
      SetCursorXY(66, 5);
      Printf("   ");
      SetCursorXY(66, 5);
      Printf("%c%d", last.set, last.id);
      LoadCursor();
    // ReleaseLock(SECTION_TERMINAL_TX);
  }

  Exit();
}

void LegendWindow() {
  WindowDraw(64, 80, 8, 22);  // legend
  
  int manager_tid = WhoIs("window-manager");

  for ( ; ; ) {
    Send(manager_tid, NULL, 0, NULL, 0);
    // update
  }

  Exit();
}

void ResetCommandLine() {
  // AcquireLock(SECTION_TERMINAL_TX);
    SetCursorXY(0, 24);
    ClearLine();
    Printf("> ");
    SetCursorXY(3, 24);
  // ReleaseLock(SECTION_TERMINAL_TX);
}

void ShellWindow() {
  // track status
  // we pass this from WindowManager() to ShellWindow() at startup
  track sts;

  // initialize command-line buffers
  char cmd_buf[CMD_BUF_SIZE+1];
  cmd_buf[0] = '\0';
  int cmd_ptr = 0;

  int reply;

  // initialize window
  WindowDraw(0, 80, 22, 24);  // command line
  // SetScrollableRegion(23, 24);
  ResetCommandLine();
  
  int manager_tid = WhoIs("window-manager");

  for ( ; ; ) {
    // message: NULL
    // reply: next character to process in shell
    Send(manager_tid, NULL, 0, (char*)&reply, sizeof(int));
    
    if (reply == '\r') {
      // AcquireLock(SECTION_TERMINAL_TX);
        cmd_buf[cmd_ptr] = '\0';
        // SetCursorXY(0, 23);
        // ClearLine();
        // Printf("> %s", cmd_buf);
      // ReleaseLock(SECTION_TERMINAL_TX);    
      // ResetCommandLine();  
      
      DoCommand(&sts, cmd_buf);
      // TODO: colorize previous command line based on result
      
      cmd_ptr = 0;
    } else if (reply == '\x7f' && cmd_ptr > 0) {
      // AcquireLock(SECTION_TERMINAL_TX);
        cmd_ptr--;
        SetCursorXY(cmd_ptr+3, 24);
        Printf(" ");
        SetCursorXY(cmd_ptr+3, 24);
      // ReleaseLock(SECTION_TERMINAL_TX);
    } else if (IsAlnum(reply) && cmd_ptr < CMD_BUF_SIZE) {
      // AcquireLock(SECTION_TERMINAL_TX);
        SetCursorXY(cmd_ptr+3, 24);
        Putc(COM2, reply);
        cmd_buf[cmd_ptr++] = reply;
      // ReleaseLock(SECTION_TERMINAL_TX);
    }
  }
  Exit();
}

/* WINDOW MANAGER */

void WindowManager() {
  // track status
  track sts;

  event msg;

  // init terminal
  WindowInit(&sts);
  
  // create windows
  // TODO: this should probably be moved outside the window manager, but
  // I'm not going to worry about that for now.
  // int track_window_tid = Create(2, &TrackWindow, "track-window");
  // Execute(track_window_tid);
  int timer_window_tid = Create(2, &TimerWindow, "timer-window");
  Execute(timer_window_tid);
  int sensor_window_tid = Create(2, &SensorWindow, "sensor-window");
  Execute(sensor_window_tid);
  // int legend_window_tid = Create(2, &LegendWindow, "legend-window");
  // Send(legend_window_tid, NULL, 0, NULL, 0);
  int shell_window_tid = Create(3, &ShellWindow, "shell-window");
  Execute(shell_window_tid);
  // create event generators
  int tick_generator_tid = Create(4, &TickGenerator, "tick-generator");
  Execute(tick_generator_tid);
  int input_generator_tid = Create(4, &InputGenerator, "input-generator");
  Execute(input_generator_tid);
  int sensor_generator_tid = Create(4, &SensorGenerator, "sensor-generator");
  Execute(sensor_generator_tid);

  // TODO: eventually, we will probably want to improve this flag system.
  int timer_waiting = FALSE;
  int timer_ready = FALSE;
  int timer_data;
  int shell_waiting = FALSE;
  int shell_ready = FALSE;
  int shell_data;
  int sensor_waiting = FALSE;
  int sensor_ready = FALSE;
  char sensor_data[10];

  int sender_tid;
  for ( ; ; ) {
    Receive(&sender_tid, (char*)&msg, sizeof(event));
    if (sender_tid == sensor_window_tid) {
      sensor_waiting = TRUE;
      if (sensor_ready) {
        Reply(sensor_window_tid, sensor_data, 10);
        sensor_waiting = FALSE;
        sensor_ready = FALSE;
      }
    } else if (sender_tid == sensor_generator_tid) {
      sensor_ready = TRUE;
      MemCopy(sensor_data, msg.cc, 10);
      if (sensor_waiting) {
        Reply(sensor_window_tid, sensor_data, 10);
        sensor_waiting = FALSE;
        sensor_ready = FALSE;
      }
      Reply(sensor_generator_tid, NULL, 0);
    } else if (sender_tid == timer_window_tid) {
      timer_waiting = TRUE;
      if (timer_ready) {
        Reply(timer_window_tid, (char*)&timer_data, sizeof(int));
        timer_waiting = FALSE;
        timer_ready = FALSE;
      }
    } else if (sender_tid == shell_window_tid) {
      shell_waiting = TRUE;
      if (shell_ready) {
        Reply(shell_window_tid, (char*)&shell_data, sizeof(int));
        shell_waiting = FALSE;
        shell_ready = FALSE;
      }
    } else if (sender_tid == tick_generator_tid) {
      timer_ready = TRUE;
      timer_data = msg.ii;
      if (timer_waiting) {
        Reply(timer_window_tid, (char*)&timer_data, sizeof(int));
        timer_waiting = FALSE;
        timer_ready = FALSE;
      }
      Reply(tick_generator_tid, NULL, 0);
    } else if (sender_tid == input_generator_tid) {
      shell_ready = TRUE;
      shell_data = msg.ii;
      if (shell_waiting) {
        Reply(shell_window_tid, (char*)&shell_data, sizeof(int));
        shell_waiting = FALSE;
        shell_ready = FALSE;
      }
      Reply(input_generator_tid, NULL, 0);
    }
  }
  Exit();
}

/* FIRST USER TASK */

void UserInit() {
  int child_tid;
  child_tid = Create(4, &NameServer, "name-server");
  Execute(child_tid);
  child_tid = Create(6, &ClockServer, "clock-server");
  Execute(child_tid);
  /*
  child_tid = Create(8, &RandomServer);
  Send(child_tid, NULL, 0, NULL, 0);
  */
  child_tid = Create(5, &TrainRxServer, "train-rx-server");
  Execute(child_tid);
  child_tid = Create(4, &TrainTxServer, "train-tx-server");
  Execute(child_tid);
  child_tid = Create(4, &TerminalRxServer, "terminal-rx-server");
  Execute(child_tid);
  child_tid = Create(5, &TerminalTxServer, "terminal-tx-server");
  Execute(child_tid);
  child_tid = Create(0, &IdleTask, "idle-task");
  Execute(child_tid);
  child_tid = Create(3, &WindowManager, "window-manager");
  Execute(child_tid);
  
  /*
  while (1) {
    int j;
    for (j = 0; j < 50; j++) {
      int k;
      for (k = 0; k < 10000; k++)
        ParamTest(0x12, 0x34, 0x56, 0x78, 0x9A);
      bwprintf(COM2, ".");
    }
    bwprintf(COM2, " %d\r\n", Time());
  }
  */
  Exit();
}
